using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.Practices.Unity.Interception
{
    [TestClass]
    public class ReturnValuesInterfaceInterceptorTest
    {
        static TInterface WrapAndCreateType<TInterface, TConcrete>(IEnumerable<KeyValuePair<MethodBase, List<IInterceptionHandler>>> handlers)
            where TConcrete : TInterface
        {
            Type wrappedType = InterfaceInterceptor.WrapInterface(typeof(TInterface));
            ILEmitProxy proxy = new ILEmitProxy(handlers);
            object target = Activator.CreateInstance(typeof(TConcrete));
            List<object> wrappedCtorArgs = new List<object> { proxy, target };
            return (TInterface)Activator.CreateInstance(wrappedType, wrappedCtorArgs.ToArray());
        }

        [TestMethod]
        public void Exception()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(ISpyReturn).GetMethod("Exception");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            ISpyReturn result = WrapAndCreateType<ISpyReturn, SpyReturn>(dictionary);

            AssertEx.Throws<ArgumentException>(result.Exception);

        }

        [TestMethod]
        public void NoReturnValue()
        {
            Recorder.Records.Clear();
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(ISpyReturn).GetMethod("NoReturnValue");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            ISpyReturn result = WrapAndCreateType<ISpyReturn, SpyReturn>(dictionary);
            result.NoReturnValue();

            Assert.AreEqual(3, Recorder.Records.Count);
            Assert.AreEqual("Before Method", Recorder.Records[0]);
            Assert.AreEqual("In Method", Recorder.Records[1]);
            Assert.AreEqual("After Method", Recorder.Records[2]);
        }

        [TestMethod]
        public void ReturnsClassType()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(ISpyReturn).GetMethod("ReturnsClassType");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            ISpyReturn result = WrapAndCreateType<ISpyReturn, SpyReturn>(dictionary);
            object retValue = result.ReturnsClassType();

            Assert.AreSame(SpyReturn.ObjectReturn, retValue);
        }

        [TestMethod]
        public void ReturnsValueType()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(ISpyReturn).GetMethod("ReturnsValueType");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            ISpyReturn result = WrapAndCreateType<ISpyReturn, SpyReturn>(dictionary);
            int retValue = result.ReturnsValueType();

            Assert.AreEqual(SpyReturn.ValueReturn, retValue);
        }

        public interface ISpyReturn
        {
            void Exception();
            void NoReturnValue();
            object ReturnsClassType();
            int ReturnsValueType();
        }

        sealed class SpyReturn : ISpyReturn
        {
            public const int ValueReturn = 42;
            public static readonly object ObjectReturn = new object();

            public void Exception()
            {
                throw new ArgumentException();
            }

            public void NoReturnValue()
            {
                Recorder.Records.Add("In Method");
            }

            public object ReturnsClassType()
            {
                return ObjectReturn;
            }

            public int ReturnsValueType()
            {
                return ValueReturn;
            }
        }
    }
}